#include "stdlib.h"
#include "unified.h"
#include "assert.h"
#include "interpreter.h"
#include "types.h"
#include <gc/gc.h>

/*
TODO:
- Remove make_generic_list(NULL)
  - Empty lists should just be NULL
- Start factoring this out into a new file structure
*/

/*
  Takes a UFILE and returns an initialised tokenizer reading from
  that stream. This should be somehow extended to accomodate a REPL.
  Tokenizer position is initialised to (1,1) - token_ equivalents are
  left uninitialised as they won't be used until a position is pushed.
*/

tokenizer_t *init_tokenizer(UFILE *filehandle, UChar *filename)
{
  tokenizer_t *tokenizer = GC_malloc(sizeof(tokenizer_t));
  
  tokenizer->in = filehandle;
  tokenizer->filename = GC_malloc((u_strlen(filename)+1) * sizeof(UChar));
  u_strcpy(tokenizer->filename, filename);
  tokenizer->line = tokenizer->saved_line = tokenizer->token_line = 1;
  tokenizer->column = tokenizer->saved_column = tokenizer->token_column = 1;
  tokenizer->multiplier = 0;
  
  return tokenizer;
}


void parse_exception(handler_t *handler, tokenizer_t *tokenizer, char *message)
{
  handler->tag = make_tag(to_unicode("syntax"));

  generic_list_t *description =
    make_generic_list(make_string(to_unicode(message)));

  generic_list_add(description, make_string(tokenizer->filename));
  generic_list_add(description, make_integer(tokenizer->line));
  generic_list_add(description, make_integer(tokenizer->column));
  
  handler->value = (value_t*)make_list(description);

  longjmp(handler->jump, 1);
}

inline UChar fget_character(tokenizer_t *tokenizer)
{
  UChar next = u_fgetc(tokenizer->in);
  if('\n' == next)
  {
    tokenizer->saved_column = tokenizer->column;
    tokenizer->column = 1;
    tokenizer->saved_line = tokenizer->line++;
  }
  else
  {
    tokenizer->saved_line = tokenizer->line;
    tokenizer->saved_column = tokenizer->column++;
  }
  return next;
}

inline void freplace_character(tokenizer_t *tokenizer, UChar replace)
{
  tokenizer->column = tokenizer->saved_column;
  tokenizer->line = tokenizer->saved_line;
  u_fungetc(replace,tokenizer->in);
}

/*
  If 'literal' is the next item in the stream, returns 1 and advances the
  stream, else returns zero and leaves the stream untouched (bar any
  behind-the-scenes buffering in utf8.c)
*/

int expect_literal(tokenizer_t *tokenizer, UChar literal, int terminal)
{
  UChar next = fget_character(tokenizer);
  if(next == literal)
  {
    if(terminal)
      ignore(tokenizer);
    
    return 1;
  }
  else
  {
    if(U_EOF != next)
      freplace_character(tokenizer,next);
    return 0;
  }
}

/*
  Convenience function for consuming whitespace and comments. All routines
  which read tokens from the input stream (as opposed to other helper routines)
  should call this after reading.
*/

void ignore(tokenizer_t *tokenizer)
{

  do
  {
    UChar next = fget_character(tokenizer);
    
    if(U_EOF == next)
    {
      /*Nothing to see here */
      return;
    }
    else if(isspace(next))
    {
      
      consume_whitespace(tokenizer);
    }
    else if('#' == next)
    {
      
      consume_comment(tokenizer);
    }
    else
    {
      freplace_character(tokenizer,next);
      return;
    }
    
  }
  while(1);

}

void consume_comment(tokenizer_t *tokenizer)
{
  int nest_count = 0;
  UChar next;
  next = fget_character(tokenizer);
  /*Nothing to see here */
  
  // Multiline comment
  if(next == '|')
  {
    nest_count++;
    while(nest_count)
    {
      next = fget_character(tokenizer);
      
      // Nested comment
      if(next == '#')
      {
        next = fget_character(tokenizer);
        
        // Multiline
        if(next == '|')
        {
          nest_count++;
        }
        // Single line
        else
        {
          next = fget_character(tokenizer);
          
          while(next != '\n')
          {
            /*End of file */
            if(U_EOF == next)
              return;
            next = fget_character(tokenizer);
            
          }
          
          
        }
      }
      // End of comment?
      else
      {
        while('|' == next)
        {
          next = fget_character(tokenizer);
       
          if(next == '#')
          {
            nest_count--;
          }
        }
      }
    }
  }
  else
  {
    while(next != '\n')
    {
      /*Nothing to see here */
      if(U_EOF == next)
        return;
      next = fget_character(tokenizer);
      
    }
    
    
  }
}

void consume_whitespace(tokenizer_t *tokenizer)
{
  UChar next;
  while(next = fget_character(tokenizer))
  {
    switch(next)
    {
      case '\n':
      case '\r':
        
        
        continue;
      default:
        if(u_isspace(next))
        {
          
          continue;
        }
        
        if(U_EOF != next)
          freplace_character(tokenizer,next);
        return;  
    }
  }
}

/*
  Convenience function to retrieve the next value in the input stream. The
  order has not been chosen for any particular reason. When we have a larger
  corpus to work with, analysis of code samples may suggest a better order.
*/

value_t *get_value(tokenizer_t *tokenizer, handler_t *handler)
{
  value_t *next;

  UChar *filename;
  int line;

  //push_position(tokenizer,&line);

  /*End of input stream */
  if(u_feof(tokenizer->in))
    return 0;
  
  else if(next = (value_t*)get_name(tokenizer,handler));
    
  /*Primitive types */
    
  else if(next = (value_t*)get_tag(tokenizer,handler));
  else if(next = (value_t*)get_number(tokenizer,handler));
  else if(next = (value_t*)get_character(tokenizer,handler));
  else if(next = (value_t*)get_string(tokenizer,handler));
    
  /*Compound types*/
  
  else if(next = (value_t*)get_list(tokenizer,handler));
  else if(next = (value_t*)get_nested_call(tokenizer,handler));
  else if(next = (value_t*)get_function(tokenizer,handler));

  /*No value to parse */
  else { return NULL; }

  //pop_position(tokenizer,next,line);

  return next;
}

/*
  Returns the next character value in the stream
  - Make it handle unicode escapes properly
*/
character_t *get_character(tokenizer_t *tokenizer, handler_t *handler)
{
  UChar next, value;
  //value = GC_malloc(sizeof(UChar));
  
  if(!expect_literal(tokenizer,'\'',0))
    return 0;
  
  next = fget_character(tokenizer);
  
  
  if('\\' == next)
  {
    next = fget_character(tokenizer);
    
    switch(next)
    {
      case 't':
        value = '\t';
        break;
      case 'n':
        value = '\n';
        break;
      default:
        value = next;
        break;
    }
  }
  else if('\'' == next)
  {
    parse_exception(handler,tokenizer,"Empty character literal");
  }
  else if(('\n' != next) && ('\r' != next))
  {
    value = next;
  }
  
  /*TODO: Assert that the last character is a closing quote */

  if(!expect_literal(tokenizer,'\'',0))
  {
    // This is an error
    parse_exception(handler,tokenizer,"Unexpected end of character");
  }

  ignore(tokenizer);

  return make_character(value);
}

/*
  Returns the next string value in the input stream
*/

string_types_t *get_string(tokenizer_t *tokenizer, handler_t *handler)
{
  UChar *string_buffer = GC_malloc(256*sizeof(UChar));
  size_t buffer_size = 256*sizeof(UChar);

  int interpolate = expect_literal(tokenizer,'$',0);

  if(!expect_literal(tokenizer,'"',0))
  {
    if(interpolate)
      // This should be fatal
      parse_exception(handler,tokenizer,"Expected string literal after $");
    return 0;
  }

  UChar next = fget_character(tokenizer);

  generic_list_t *interpolated_values = make_generic_list(NULL);

  while('"' != next)
  {
    

    if('\\' == next)
    {
      // Leave things be
      buffer_append_char(&string_buffer, next, &buffer_size);
      next = fget_character(tokenizer);
      if(!next || (U_EOF == next))
      {
        parse_exception(handler,tokenizer,"Unexpected end of string");
      }
      buffer_append_char(&string_buffer, next, &buffer_size);
    }
    else if(interpolate && ('{' == next))
    {
      string_t *out_string = make_string(string_buffer);
      generic_list_add(interpolated_values, out_string);
      
      *string_buffer = 0;
      
      name_t *name = get_name(tokenizer,handler);
      
       if(!name)
      {
        fprintf(stderr,"Expected name...");
        parse_exception(handler,tokenizer,
          "Expected name in interpolated string");
      }
     
      if(expect_literal(tokenizer,'}',1))
      {
        generic_list_add(interpolated_values, name);
      }
      else
      {
        /*Error - Unexpected end of name or extraneous symbols */
        parse_exception(handler,tokenizer,
          "Unexpected end of string");
      }
      
    }
    else if('\n' == next)
    {
      /*Error - Unescaped linebreaks not permitted in strings */
      parse_exception(handler,tokenizer,
          "Unexpected end of string");
    }
    else
    {
      buffer_append_char(&string_buffer,next,&buffer_size);
    }
    next = fget_character(tokenizer);
  }
  

  ignore(tokenizer);
  
  if(interpolate)
  {
    string_t *last_string = make_string(string_buffer);
    generic_list_add(interpolated_values,last_string);
    return (string_types_t*)make_interpolate(interpolated_values);
  }
  else
  {
    return (string_types_t*)make_string(string_buffer);
  }

}

number_t *get_number(tokenizer_t *tokenizer, handler_t *handler)
{
  UChar next = fget_character(tokenizer);
  
  
  if(!isdigit(next))
  {
    if(U_EOF != next)
      freplace_character(tokenizer,next);
    return 0;
  }

  size_t buffer_size = 256;
  UChar *string = (UChar*)GC_malloc(buffer_size*sizeof(UChar));
  
  if(tokenizer->multiplier)
    buffer_append_char(&string,'-',&buffer_size);
  tokenizer->multiplier = 0;

  do
  {
    
    buffer_append_char(&string,next,&buffer_size);
    next = fget_character(tokenizer);
  }
  while(isdigit(next));
  
  number_t *number;
  
  if('.' == next)
  {
    buffer_append_char(&string,'.',&buffer_size);
    next = fget_character(tokenizer);
    do
    {
      
      buffer_append_char(&string,next,&buffer_size);
      next = fget_character(tokenizer);
    }
    while(isdigit(next));
    
    number = (number_t*)make_float_str(string);
  }
  else
  {
    number = (number_t*)make_integer_str(string);
  }
  
  if(U_EOF != next)
  {
    freplace_character(tokenizer,next);
  }
  
  ignore(tokenizer);
  
  return number;
}


/*
  Returns the next tag in the stream
*/

tag_t *get_tag(tokenizer_t *tokenizer, handler_t *handler)
{
  if(!expect_literal(tokenizer,'.',0))
    return 0;
  
  UChar *value = get_identifier(tokenizer,handler);
  
  if(!value)
    // This must be an error
    parse_exception(handler,tokenizer,
      "Expected identifier after .");
 
  ignore(tokenizer);
 
  return make_tag(value);
}

/*
  Returns the next name in the stream
*/

name_t *get_name(tokenizer_t *tokenizer, handler_t *handler)
{    
  name_t *new_name;
  
  UChar *next = get_identifier(tokenizer,handler);
  
  if(!next)
  {
    return 0;
  }
  
  new_name = make_name(next);
  
  if(expect_literal(tokenizer,'.',0))
  {

    new_name = make_lookup(next);

    new_name->parts = make_generic_list(NULL);
    
    do
    {
      next = get_identifier(tokenizer,handler);
      if(next)
      {
        generic_list_add(new_name->parts,next);
      }
      else
      {
        // Handle err0r
        parse_exception(handler,tokenizer,"Unexpected end of dotted name");
      }
    }
    while(expect_literal(tokenizer,'.',0));
  }
  else
  {
    new_name = make_name(next);
  }

  ignore(tokenizer);

  return new_name;
}

/*
  Returns the next identifier in the stream as a utf8 character string
*/

UChar *get_identifier(tokenizer_t *tokenizer, handler_t *handler)
{
  UChar *string;
  UChar next;
  size_t buffer_size = 256;

  next = fget_character(tokenizer);

  if(!starts_identifier(next))
  {
    freplace_character(tokenizer,next);
    return 0;
  }

  //

  string = GC_malloc(buffer_size*sizeof(UChar));

  if('-' == next)
  {
    if(isdigit(fget_character(tokenizer)))
    {
      tokenizer->multiplier = 1;
      freplace_character(tokenizer,next);
      return 0;
    }
    freplace_character(tokenizer,next);
  }

  while(in_identifier(next))
  {
    
    buffer_append_char(&string, next, &buffer_size);
    next = fget_character(tokenizer);
  }
  

  if(next != U_EOF)
    freplace_character(tokenizer,next);
  
  return string;
}

/*
  Stores the current position in the file
*/

void push_position(tokenizer_t *tokenizer, int *line)
{
  *line = tokenizer->line;
}

/*
  Gets the current position in the file
*/

int get_position(tokenizer_t *tokenizer)
{
  return tokenizer->line;
}

/*
  Gets the last stored position in the file
  and attaches it to the provided value
*/

void set_position(tokenizer_t *tokenizer, int line_number, call_t *call)
{
  call->filename = tokenizer->filename;
  call->line_number = line_number;
}

/*
void pop_position(tokenizer_t *tokenizer, value_t *value, int line)
{
  assert(has_type(value, type_call));
  (value->generic).data->filename = tokenizer->filename;
  (value->generic).data->line = line;
}
*/

/*
  Returns the position of a value as a triple of:
  - filename
  - line
  - column
*/
/*
generic_list_t *value_position(value_t *value)
{
  generic_list_t *data;

  if(value->generic.data)
  {
    if(value->generic.data->filename)
      data = make_generic_list(make_string(value->generic.data->filename));
    else
      data = make_generic_list(make_string(to_unicode("<unknown>")));
    generic_list_add(data, make_integer(value->generic.data->line));
    generic_list_add(data, make_integer(value->generic.data->column));
  }
  else
  {
    data = make_generic_list(make_string(to_unicode("<unknown>")));
    generic_list_add(data, make_integer(0));
    generic_list_add(data, make_integer(0));
  }

  return data;
}*/

int starts_identifier(UChar next)
{
  // alphanum + "*<>+-/%=?^!"
  return isalpha(next)
    || (60 <= next && next <= 63)
    || 45 == next
    || 47 == next
    || 37 == next
    || 42 == next
    || 43 == next
		|| 33 == next
    || 95 == next;
}

int in_identifier(UChar next)
{
  return starts_identifier(next) || isdigit(next);
}

/*
  Note: This function implements an ugly bit of the grammar!

  Returns the next function value in the input stream. This one is tricky as
  we may have to backtrack if no arguments are found.
*/

function_t *get_function(tokenizer_t *tokenizer, handler_t *handler)
{
  int recursive = 0;
  ignore(tokenizer);
  if(expect_literal(tokenizer,'@',0))
  {
    recursive = 1;
    if(!expect_literal(tokenizer,'{',1))
    {
      parse_exception(handler,tokenizer, "Expected function literal after @");
    }
  }
  else if(!expect_literal(tokenizer,'{',1))
  {
    return 0;
  }
  
  int spill;
  generic_list_t *parameters;
  name_t *var_args;

  int line;

  int line_number = get_position(tokenizer);
  //push_position(tokenizer, &line);

  parameters = get_parameters(tokenizer,handler, &var_args, &spill);
 
  generic_list_t *call_list;
  
  if(!spill)
  {
    /*get_parameters checks for : */
    call_list = get_function_body(tokenizer,handler,NULL);
   if(0 == parameters->length)
      parameters = 0;
  }
  else
  {
    if(!parameters->length)
    {
      parse_exception(handler,tokenizer,"Empty function body");
    }
    value_t *head = generic_list_remove(parameters);
    call_t *spilt = make_call(head,parameters);
    set_position(tokenizer, line_number, spilt);
    //pop_position(tokenizer, (value_t*)spilt, line);
    //spilt->data = head->generic.data;
    if(expect_literal(tokenizer,';',1))
      call_list = get_function_body(tokenizer,handler,spilt);
    else
    {
      ignore(tokenizer);
      call_list = make_generic_list(spilt);
    }
    parameters = 0;
  }
  
  if(!expect_literal(tokenizer,'}',1))
  {
    parse_exception(handler,tokenizer,"Unexpected end of function body");
  }
  
  function_t *new_function = make_function(parameters,var_args,call_list);
  new_function->recursive = recursive;
  return new_function;
}

/*
  Note: This function implements an ugly bit of the grammar!
  
  Gets the parameter list for a function literal. If the function turns out to
  have no parameters, 'spill' is set and the returned values are used in the
  construction of the first call in the function's body.
*/

generic_list_t *get_parameters(tokenizer_t *tokenizer, handler_t *handler, name_t **var_args, int *spill)
{
  name_t *next;
  generic_list_t *parameters = make_generic_list(NULL);
  
  *var_args = 0;
  *spill = 0;

  int line;
  
  //push_position(tokenizer,&line);

  if(next = get_name(tokenizer, handler))
  {
    //pop_position(tokenizer,(value_t*)next,line);
    generic_list_add(parameters, next);

    while(next = get_name(tokenizer,handler))
    {
      generic_list_add(parameters, next);
    }
  }

  
  if(expect_literal(tokenizer,'~',1))
  {
    *var_args = get_name(tokenizer,handler);
  }
  
  if(!expect_literal(tokenizer,':',1))
  {
    if(*var_args)
    {
      parse_exception(handler,tokenizer,"Expected tail argument after '~'");
      return 0;
    }
    
    *spill = 1;
    value_t *next_value;
    while(next_value = get_value(tokenizer,handler))
    {
      generic_list_add(parameters,next_value);
    }

  }
  
  return parameters;
}

/*
  Returns the list of calls making up the body of a function. If spill is set,
  it is used as the first item of the call list.
*/

generic_list_t *get_function_body(tokenizer_t *tokenizer, handler_t *handler, call_t *spill)
{
  generic_list_t *call_list;
  call_t *call = 0;
  
  call_list = make_generic_list(spill);
  
  int expecting_next = 1;
  
  while(expecting_next && (call = get_call(tokenizer,handler)))
  {
    generic_list_add(call_list, (void*)call);
    expecting_next = expect_literal(tokenizer, ';',1);
  }
  
  ignore(tokenizer);
  
  return call_list;
}


list_types_t *get_list(tokenizer_t *tokenizer, handler_t *handler)
{
    
  if(!expect_literal(tokenizer,'[',1))
  {
    return 0;
  }
  
  // Don't allocate storage until we know there's a list here
  generic_list_t *list_items = make_generic_list(NULL);

  value_t *next;
  
  while(next = get_value(tokenizer,handler))
  {
    generic_list_add(list_items, next);
  }

  // No items to be appended
  if(!expect_literal(tokenizer, '~', 1))
  {
    list_t *new_list;

    if(!expect_literal(tokenizer, ']', 1))
      parse_exception(handler,tokenizer,
        "Unexpected end of list");

    if(list_items->length)
      new_list = make_list(list_items);
    else
      new_list = make_list(NULL); // Empty list

    return (list_types_t*)new_list;
  }

  generic_list_t *appended_lists;

  // No items before the append
  if(!list_items->length)
		appended_lists = make_generic_list(NULL);
	else
		appended_lists = make_generic_list(make_list(list_items));
  
  while(next = get_value(tokenizer,handler))
  {
    if(!has_type(next, type_list) || next->list.items)
    {
      generic_list_add(appended_lists, next);
    }
  }

  append_t *appendage;

  if(0 == appended_lists->length)
  {
    // empty list
    appendage = (append_t*)make_list(NULL);
  }
  else if(1 == appended_lists->length)
  {
    // singleton list
    appendage = (append_t*)appended_lists->first->head;
  }
  else
  {
    appendage = make_append(appended_lists);
  }

  if(!expect_literal(tokenizer,']',1))
  {
        parse_exception(handler,tokenizer, "Unexpected end of list");
  }

  ignore(tokenizer);
  
  return (list_types_t*)appendage;
}

/*
  Enforces parentheses around a call
*/

call_t *get_nested_call(tokenizer_t *tokenizer, handler_t *handler)
{
  call_t *call;
  
  if(!expect_literal(tokenizer,'(',1))
  {
    return 0;
  }
  
  call = get_call(tokenizer,handler);

  if(!call)
    parse_exception(handler,tokenizer,"Empty call");

  if(!expect_literal(tokenizer,')',1))
  {
    parse_exception(handler,tokenizer, "Unmatched parenthesis in call");
  }
  return call;
}

/*
  Returns the next call in the input stream
*/

call_t *get_call(tokenizer_t *tokenizer,handler_t *handler)
{
  call_t *new_call = 0;

  int line_number;

  line_number = get_position(tokenizer);
  //push_position(tokenizer, &line_number);

  value_t *head = get_value(tokenizer,handler);
  
  if(NULL == head)
  {
    return NULL;
  }
  value_t *next;

  generic_list_t *arguments = 0;

  if(next = get_value(tokenizer,handler))
  {
    arguments = make_generic_list(next);

    while(next = get_value(tokenizer,handler))
    {
      generic_list_add(arguments, next);
    }
  }

  new_call = make_call(head, arguments);
 
  set_position(tokenizer, line_number, new_call);
  //pop_position(tokenizer, (value_t*)new_call, line_number);
  //new_call->data = head->generic.data;

  return new_call;  
}

/*
  Returns a function taking a single argument "arguments" which is the list of
  parameters passed on the command line. 'filehandle' is a pointer to an open
  UTF8 stream. This should be somehow extended to accomodate a REPL.
*/

function_t *parse(UFILE *filehandle, UChar *filename, handler_t *handler)
{
  function_t *main;
  
  UChar *arguments = to_unicode("arguments");
  name_t *u_arguments = make_name(arguments);
  generic_list_t *argument_list = make_generic_list(u_arguments);
  
  tokenizer_t *tokenizer = init_tokenizer(filehandle,filename);
  ignore(tokenizer);
  generic_list_t *call_list = get_function_body(tokenizer,handler, NULL);
 
  if(!u_feof(tokenizer->in))
  {
    parse_exception(handler, tokenizer, "Garbage at end of input");
  }
 
  main = make_function(argument_list, NULL, call_list);
  
  return main;
}
